package com.lou1052.redis.jedis.ext.client.shard;

import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.PooledObjectFactory;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.apache.commons.pool2.impl.GenericObjectPoolExt;
import redis.clients.jedis.JedisShardInfo;
import redis.clients.jedis.exceptions.JedisConnectionException;
import redis.clients.jedis.exceptions.JedisException;
import redis.clients.util.Hashing;
import redis.clients.util.IPool;
import redis.clients.util.Pool;

import java.util.Deque;
import java.util.List;
import java.util.function.Supplier;
import java.util.regex.Pattern;

/**
 * Created with IntelliJ IDEA.
 * User: liuqing
 * Date: 2015/10/25
 * Time: 22:22
 * To change this template use File | Settings | File Templates.
 */
public class ShardedJedisExtPool extends Pool<ShardedJedisExt> implements IPool<ShardedJedisExt> {

    protected GenericObjectPoolExt<ShardedJedisExt> internalPool;

    ///
    public ShardedJedisExtPool(final GenericObjectPoolConfig poolConfig, List<JedisShardInfo> shards, Supplier<Deque<PooledObject<ShardedJedisExt>>> dequeSupplier) {
        this(poolConfig, shards, Hashing.MURMUR_HASH,dequeSupplier);
    }

    public ShardedJedisExtPool(final GenericObjectPoolConfig poolConfig, List<JedisShardInfo> shards,
                               Hashing algo, Supplier<Deque<PooledObject<ShardedJedisExt>>> dequeSupplier) {
        this(poolConfig, shards, algo, null,dequeSupplier);
    }

    public ShardedJedisExtPool(final GenericObjectPoolConfig poolConfig, List<JedisShardInfo> shards,
                               Pattern keyTagPattern, Supplier<Deque<PooledObject<ShardedJedisExt>>> dequeSupplier) {
        this(poolConfig, shards, Hashing.MURMUR_HASH, keyTagPattern, dequeSupplier);
    }

    public ShardedJedisExtPool(final GenericObjectPoolConfig poolConfig, List<JedisShardInfo> shards,
                               Hashing algo, Pattern keyTagPattern, Supplier<Deque<PooledObject<ShardedJedisExt>>> dequeSupplier) {
        initPool(poolConfig, new ShardedJedisExtFactory(shards, algo, keyTagPattern), dequeSupplier);
    }

    @Override
    public void close() {
        destroy();
    }

    public boolean isClosed() {
        return this.internalPool.isClosed();
    }

    public void initPool(final GenericObjectPoolConfig poolConfig, PooledObjectFactory<ShardedJedisExt> factory,Supplier<Deque<PooledObject<ShardedJedisExt>>> dequeSupplier) {

        if (this.internalPool != null) {
            try {
                closeInternalPool();
            } catch (Exception e) {
            }
        }

        this.internalPool = new GenericObjectPoolExt<>(factory, poolConfig,dequeSupplier);
    }

    public ShardedJedisExt getResource() {
        try {
            return internalPool.borrowObject();
        } catch (Exception e) {
            throw new JedisConnectionException("Could not get a resource from the pool", e);
        }
    }

    /**
     * @deprecated starting from Jedis 3.0 this method won't exist. Resouce cleanup should be done
     *             using @see {@link redis.clients.jedis.Jedis#close()}
     */
    @Deprecated
    public void returnResourceObject(final ShardedJedisExt resource) {
        if (resource == null) {
            return;
        }
        try {
            internalPool.returnObject(resource);
        } catch (Exception e) {
            throw new JedisException("Could not return the resource to the pool", e);
        }
    }

    public void returnBrokenResource(final ShardedJedisExt resource) {
        if (resource != null) {
            returnBrokenResourceObject(resource);
        }
    }

    public void returnResource(final ShardedJedisExt resource) {
        if (resource != null) {
            returnResourceObject(resource);
        }
    }

    public void destroy() {
        closeInternalPool();
    }

    protected void returnBrokenResourceObject(final ShardedJedisExt resource) {
        try {
            internalPool.invalidateObject(resource);
        } catch (Exception e) {
            throw new JedisException("Could not return the resource to the pool", e);
        }
    }

    protected void closeInternalPool() {
        try {
            internalPool.close();
        } catch (Exception e) {
            throw new JedisException("Could not destroy the pool", e);
        }
    }

    public int getNumActive() {
        if (poolInactive()) {
            return -1;
        }

        return this.internalPool.getNumActive();
    }

    public int getNumIdle() {
        if (poolInactive()) {
            return -1;
        }

        return this.internalPool.getNumIdle();
    }

    public int getNumWaiters() {
        if (poolInactive()) {
            return -1;
        }

        return this.internalPool.getNumWaiters();
    }

    public long getMeanBorrowWaitTimeMillis() {
        if (poolInactive()) {
            return -1;
        }

        return this.internalPool.getMeanBorrowWaitTimeMillis();
    }

    public long getMaxBorrowWaitTimeMillis() {
        if (poolInactive()) {
            return -1;
        }

        return this.internalPool.getMaxBorrowWaitTimeMillis();
    }

    private boolean poolInactive() {
        return this.internalPool == null || this.internalPool.isClosed();
    }

    public void addObjects(int count) {
        try {
            for (int i = 0; i < count ; i++) {
                this.internalPool.addObject();
            }
        } catch (Exception e) {
            throw new JedisException("Error trying to add idle objects", e);
        }
    }
}
